package net.yuyoo.uusession.filter;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import net.yuyoo.uusession.HttpUUSessionAdapter;
import net.yuyoo.uusession.UURequestWrapper;
import net.yuyoo.uusession.UUSession;
import net.yuyoo.uusession.UUSessionServer;

 

/**
 * 
 * @author zhandl
 * CreateTime: 2008-9-29 下午02:01:22
 */
public class UUSessionFilter implements Filter {
	 
	public UUSessionServer server = null;
	
	public String sessionName = null;
	private String sessionCode = null;	
	
	private String cookieDomain = null;
	private String cookiePath = "/";
	
	private String stampName = null;
	private String stampCode = null;	
	 
	private String lastAccessLifeIntervalName = null;
	private String lastAccessLifeIntervalCode = null;
	
	private String lastAccessInstanceName = null;
	private String lastAccessInstanceCode = null;
	
	private boolean isDebug = false;
	
	private String currentInstance = null;
	
	public void destroy() { 		
	}

	public void doFilter(ServletRequest request, ServletResponse response,
			FilterChain fc) throws IOException, ServletException { 
		
		HttpServletRequest req = (HttpServletRequest) request;
		HttpServletResponse res = (HttpServletResponse) response;
		 		
		String sessionId = null;
		String stampStr = null;
		String lastAccessLifeStr = null;
		String lastInstanceHashcode = null;
		boolean currentStickFlag = false;
		
		Cookie cookies[] = req.getCookies();
		Cookie sCookie = null;
		String cookieName = null;
		     
		if (null != cookies) {

			boolean sessionSeekFlag = true;
			boolean stampSeekFlag = true;
			boolean accessLifeSeekFlag = true;
			boolean stickedInstanceFlag = true;
			
			for (int i = 0; i < cookies.length; i++) {
				sCookie = cookies[i];
				cookieName = sCookie.getName();
				if (sessionSeekFlag && cookieName.equals(this.sessionCode)) { 
					sessionId = sCookie.getValue();
					sessionSeekFlag = false;
				} else if (stampSeekFlag && cookieName.equals(this.stampCode)) {
					stampStr = sCookie.getValue();
					stampSeekFlag = false;
				} else if (accessLifeSeekFlag && cookieName.equals(this.lastAccessLifeIntervalCode)) {
					lastAccessLifeStr = sCookie.getValue();
					accessLifeSeekFlag = false;
				} else if (stickedInstanceFlag && cookieName.equals(this.lastAccessInstanceCode)) {

					String lastInstance = sCookie.getValue();
					currentStickFlag = this.currentInstance.equals(lastInstance);
					stickedInstanceFlag = false;
				}
			} 	
		}
		
		long stamp = -1;
		if (null != sessionId && null != stampStr && null != lastAccessLifeStr) {
			
			stamp = Long.parseLong(stampStr);		
			long lastAccessLife = Long.parseLong(lastAccessLifeStr);		
			
			// --- 检测是否会话过期 ---
			boolean timeoutFlag = this.isSessionTimeout(stamp, lastAccessLife) ;		
			if (timeoutFlag) {
				sessionId = this.server.newSessionId();			
			} 
		} else {
			sessionId = this.server.newSessionId();	
		}
		
		UUSession us = this.server.getSession(sessionId); 
		HttpUUSessionAdapter adapter = new HttpUUSessionAdapter(req.getSession(), us, currentStickFlag);		
		UURequestWrapper uuReq = new UURequestWrapper(adapter, req);
		
		long curServerStamp = this.server.getCurServerStamp();
 
		if (stamp > curServerStamp) {
			this.server.syncStampTask(); // --- 更新当前实例的会话服务器的时间戳 ---
		} else if (stamp < curServerStamp) {
			UUSession.delaySessionTimeout(us); // --- 延迟当前会话的过期时间 ---
		}
		 
		fc.doFilter(uuReq.getRequest(), res);
		
		this.postFilterProcess(us, res);
	}

	public void init(FilterConfig fc) throws ServletException { 
		
		String serverName = fc.getInitParameter("serverName");
		server = UUSessionServer.getInstance(serverName);
		
		sessionName = fc.getInitParameter("sessionName");
		stampName = fc.getInitParameter("stampName");
		lastAccessLifeIntervalName = fc.getInitParameter("lastAccessLifeIntervalName");
		lastAccessInstanceName =  fc.getInitParameter("lastAccessInstanceName");
		
		this.isDebug = "debug".equals(fc.getInitParameter("runningMode"));
		
		if (this.isDebug) {
			sessionCode = sessionName;
			stampCode = stampName;
			lastAccessLifeIntervalCode = lastAccessLifeIntervalName;
			lastAccessInstanceCode = lastAccessInstanceName;
			
			this.currentInstance = this.server.getInstanceName();
		} else {
			
			sessionCode = Integer.toString(sessionName.hashCode());		
			stampCode = Integer.toString(stampName.hashCode());		
			lastAccessLifeIntervalCode = Integer.toString(lastAccessLifeIntervalName.hashCode());
			lastAccessInstanceCode = Integer.toString(lastAccessInstanceName.hashCode());
			
			this.currentInstance = this.server.getInstanceHashcode();
		}
		
		this.cookieDomain = fc.getInitParameter("cookieDomain");
		if (this.cookieDomain == null) {
			this.cookieDomain = "";
		}

		this.cookiePath = fc.getInitParameter("cookiePath");
		if (this.cookiePath == null || this.cookiePath.length() == 0) {
			this.cookiePath = "/";
		}
	} 
	
	private boolean postFilterProcess(UUSession us, HttpServletResponse res) {
		 
		//  --- 设置会话ID Cookie ---
		Cookie sessionIdCookie = new Cookie(this.sessionCode, us.getId());
		sessionIdCookie.setMaxAge(-1);
		sessionIdCookie.setDomain(this.cookieDomain);
		sessionIdCookie.setPath(this.cookiePath);
		res.addCookie(sessionIdCookie);
		
		// --- 设置会话服务器时间戳 ---
		Cookie sessionStampCookie = new Cookie(this.stampCode, Long.toString(us.getSessionServer().getCurServerStamp()));
		sessionStampCookie.setMaxAge(-1);
		sessionStampCookie.setDomain(this.cookieDomain);
		sessionStampCookie.setPath(this.cookiePath);
		res.addCookie(sessionStampCookie);		
		
		// --- 设置在此实例最后访问间隔 ---
		long liusTime = us.getSessionServer().getLastInstanceUpdateStampTime();
		long now = System.currentTimeMillis();
		long lastAccessInterval = now - liusTime;
		Cookie lastAccessLifeIntervalCookie = new Cookie(this.lastAccessLifeIntervalCode, Long.toString(lastAccessInterval));
		lastAccessLifeIntervalCookie.setMaxAge(-1);
		lastAccessLifeIntervalCookie.setDomain(this.cookieDomain);
		lastAccessLifeIntervalCookie.setPath(this.cookiePath);
		res.addCookie(lastAccessLifeIntervalCookie);
		
		// --- 设置当前实例 ---
		Cookie lastIntanceCookie = new Cookie(this.lastAccessInstanceCode, this.currentInstance);
		lastIntanceCookie.setMaxAge(-1);
		lastIntanceCookie.setDomain(this.cookieDomain);
		lastIntanceCookie.setPath(this.cookiePath);
		res.addCookie(lastIntanceCookie);
		return true;
	}

	/**
	 * 判断会话是否过期.
	 * @param stamp --  当前客户端的会话时间戳
	 * @param lastAcessLife -- 当前客户端最后访问间隔
	 * @return -- 是否过期标记
	 */
	private boolean isSessionTimeout(long stamp, long lastAccessLife) {
		
		long maxPossibleLifeTime = this.server.getSessionTimeout() + this.server.getUpdateInterval();
		
		long curInstanceStamp = this.server.getCurServerStamp();		
		long minusValue = curInstanceStamp - stamp;
		
		long validLifeTime = minusValue * this.server.getUpdateInterval() - lastAccessLife;
		long aliveFlag = this.server.getSessionTimeout() - validLifeTime;
		
		// --- 正常访问情况:会话未超时 ---
		if (aliveFlag > 0 && aliveFlag <= maxPossibleLifeTime) {
			return false;
		} else if (aliveFlag <= 0){ // --- 会话超时情况 ---
			return true;
		} else if (aliveFlag > maxPossibleLifeTime) { // --- 实例异常:未正确更新当前实例的会话服务器信息 ---
			StringBuilder sb = new StringBuilder();
			sb.append("stamp:").append(stamp);
			sb.append(",lastAccessLife:").append(lastAccessLife);
			sb.append(",curInstanceStamp:").append(curInstanceStamp);
			sb.append(",validLifeTime:").append(validLifeTime);
			sb.append(",aliveFlag:").append(aliveFlag);
			sb.append(", maxPossibleLifeTime:").append(maxPossibleLifeTime); 
			throw new RuntimeException("实例异常:未正确更新当前实例的会话服务器信息." + sb.toString());
		} 
		return true;
	}
}
